Skip to content

Work around rustc 1.93 perf regression with MaybeUninit#410

Merged
emilio merged 1 commit into
servo:v1from
glandium:maybeuninit-workaround
Jun 11, 2026
Merged

Work around rustc 1.93 perf regression with MaybeUninit#410
emilio merged 1 commit into
servo:v1from
glandium:maybeuninit-workaround

Conversation

@glandium

Copy link
Copy Markdown

Fixes #408

@emilio emilio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the unsafe here seems gratuitous. Can you apply:

diff --git a/src/lib.rs b/src/lib.rs
index f7a2dff..446d736 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -653,7 +653,7 @@ impl<A: Array> SmallVecData<A> {
         // memset over the whole struct. Initializing the field via a raw pointer write breaks
         // the GVN constant-propagation chain and restores the expected codegen.
         let mut data = SmallVecData {
-            inline: unsafe { MaybeUninit::uninit().assume_init() },
+            inline: core::mem::ManuallyDrop::new(MaybeUninit::uninit()),
         };
         unsafe { core::ptr::addr_of_mut!(*data.inline).write(inline) };
         data
@@ -723,7 +723,7 @@ impl<A: Array> SmallVecData<A> {
         // `MaybeUninit::uninit()` as a global constant, which LLVM then collapses into a
         // memset over the whole struct. Constructing with a fresh uninit payload first and
         // then overwriting via a raw pointer write breaks the GVN chain.
-        let mut data = SmallVecData::Inline(unsafe { MaybeUninit::uninit().assume_init() });
+        let mut data = SmallVecData::Inline(MaybeUninit::uninit());
         match &mut data {
             SmallVecData::Inline(a) => unsafe { core::ptr::write(a as *mut _, inline) },
             _ => unsafe { core::hint::unreachable_unchecked() },

Looks good to me with that.

@glandium

Copy link
Copy Markdown
Author

I'm not sure why this is not happening with cargo bench, but the more broad test reduced from Firefox still does trigger the bug with your suggestion: https://rust.godbolt.org/z/Yq4rasTx4

Comment thread src/lib.rs Outdated
// memset over the whole struct. Initializing the field via a raw pointer write breaks
// the GVN constant-propagation chain and restores the expected codegen.
let mut data = SmallVecData {
inline: unsafe { MaybeUninit::uninit().assume_init() },

@nicoburns nicoburns Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it valid to call assume_init() here? Docs (https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#safety) say that calling assume_init() on uninitialized data is immediate UB. And to me it looks like this data could be uninitialized?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're overwriting the data immediately.

@nicoburns nicoburns Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but I thought you had to overwrite the data before calling assume_init() (and that this is the purpose of MaybeUninit: to allow you to have write access to an uninitialized buffer (so that you can initialize it) without that being UB)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoburns This also tripped me up when I looked at it, but it seems fine. It's not about overwriting the data, IMO. The types here are: MaybeUninit<ManuallyDrop<MaybeUninit<A>>>::uninit().assume_init().

It's always safe to assume_init a ManuallyDrop<MaybeUninit<..>>... It should be redundant, but see the discussion above, it seems to matter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, there's an inner MaybeUninit? Yeah, I think that makes it fine. I would love to see a SAFETY comment to that effect if possible.

@emilio emilio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty gross, but I guess it's fine. Once this is fixed in a stable Rustc we should revert the change of course...

@glandium glandium force-pushed the maybeuninit-workaround branch from 263ee34 to 99e1c6d Compare June 11, 2026 10:57

@emilio emilio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=me

@emilio emilio merged commit 51b965f into servo:v1 Jun 11, 2026
4 of 7 checks passed
@emilio emilio mentioned this pull request Jun 11, 2026
emilio added a commit that referenced this pull request Jun 11, 2026
 * #397 - Exclude development script.
 * #407 - Fix use-after-free in DrainFilter::keep_rest for SmallVec with capacity 0.
 * #410 - Work around rustc performance regression.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants